package it.unicam.cs.cosy.bioshape.core.behaviours;

import hermesV3.util.HermesLogger;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Stack;

/**
 * Checks the correctness of the {@code Behaviour} processes given as input and
 * improves processes towards simulation. Several optimisations and sanity
 * checks are executed:
 * <ol>
 * <li>Checks undefined behaviour (dangling process variables)</li>
 * <li>Links occurrence of variables to the corresponding process</li>
 * <li>Compress consecutive delay operators</li>
 * <li>Checks for channel compatibility</li>
 * </ol>
 * 
 * The table is populated during the parsing process.
 * 
 * @author Federico Buti, Massimo Callisto De Donato
 */
public class BehavioursTable implements Serializable {

	/** Auto-generated serial ID */
	private static final long serialVersionUID = 4265165992274352752L;
	/** Represents the set of tuples contained in the table. */
	protected LinkedList<BehaviourTuple> processes;

	/** Represents the set of tuples contained in the table. */
	public BehavioursTable() {
		processes = new LinkedList<BehaviourTuple>();
	}

	/**
	 * Builds the stacks of read processes. One contains processes which are
	 * guarded. i.e. no free process variables are present as active processes.
	 * Such processes are used to link free variable in processes belonging to
	 * the stack of incomplete processes.
	 */
	private List<Stack<BehaviourTuple>> buildStacks() {
		Stack<BehaviourTuple> S_complete = new Stack<BehaviourTuple>();
		Stack<BehaviourTuple> S_incomplete = new Stack<BehaviourTuple>();
		for (BehaviourTuple t : processes)
			if (t.isGuarded()) {
				if (t.isVisible()) {
					S_complete.push(t);
					t.setVisible(false);
				}
			} else
				S_incomplete.push(t);
		List<Stack<BehaviourTuple>> tmp = new ArrayList<Stack<BehaviourTuple>>(
				2);
		tmp.add(0, S_complete);
		tmp.add(1, S_incomplete);
		return tmp;
	}

	/***/
	private void resolveDependencies() throws BehaviourException {
		List<Stack<BehaviourTuple>> tmp;
		Stack<BehaviourTuple> S_complete, S_incomplete;
		do {
			tmp = buildStacks();
			S_complete = tmp.get(0);
			S_incomplete = tmp.get(1);
			if (S_complete.size() == 0 && S_incomplete.size() != 0) {
				String msg = "SCParser - An infinite behaviour has been "
						+ "detected! ";
				msg += "Conflicting processes: ";
				for (BehaviourTuple tp : S_incomplete)
					msg += "[" + tp.getName() + "] ";
				msg += "\n";
				throw new BehaviourException(msg);
			} // else...
			while (!S_complete.isEmpty()) {
				BehaviourTuple t = S_complete.pop();
				Variable p = t.getState();
				for (BehaviourTuple t2 : processes) {
					for (Iterator<Variable> i = t2.dependencies.iterator(); i
							.hasNext();) {
						Variable pv = i.next();
						if (pv.getLabel().equals(p.getLabel())) {
							pv.component = p.getNext();
							// pv.parent.setVariable(variable);
							i.remove();
						}
					}
				}
			}
		} while (S_incomplete.size() != 0);
		// restore visibility
		for (BehaviourTuple line : processes)
			line.setVisible(true);
	}

	/**
	 * Adds a row in the {@code BehaviourTable}. A row is a {@code PafasTuple}
	 * object that represents a data structure containing the name of the row,
	 * the corresponding PAFAS process, the dependencies list of
	 * {@code Variable} object in it. Also, there is a column that defines if
	 * the process has a finite behaviour. If something goes wrong during the
	 * tuple insertion, namely the first process of the behaviour is NOT a
	 * {@code Variable} object the tuple is not added and the method returns
	 * {@code false}.
	 * 
	 * @param name
	 *            the name of the PAFAS process for the current row. The name is
	 *            used as a key to look for this PAFAS process
	 * @param state
	 *            field representing the PAFAS process associated with the
	 *            <code>name</code> field
	 * @param dependenciesVars
	 *            the dependencies list represents the list of processes name
	 *            involved in this PAFAS process
	 * @return {@code true} if the behaviour is correctly set in the table,
	 *         {@code false} otherwise
	 */
	public boolean addRow(String name, Behaviour state,
			List<Behaviour> dependenciesVars) {
		try {
			processes.add(new BehaviourTuple(name, state, dependenciesVars));
		} catch (BehaviourException e) {
			HermesLogger.log(HermesLogger.ERROR, "SCParser - on top only "
					+ "variables are allowed. Reconsider the behaviour \""
					+ name + "\"!");
			return false;
		}
		return true;
	}

	/**
	 * Checks if all PAFAS processes are correct. The method iterates over the
	 * {@code PafasTuple} and tries to find the next PAFAS process with a finite
	 * behaviour. If one is found, all the processes that have it in their own
	 * dependencies list are updated. Updating consist in clearing the
	 * correspondent{@code Variable} and inserting the finite process in exam.
	 * During the iteration on the table, if there are not finite processes and
	 * some process has not a finite behaviour, the method fails to set the
	 * dependencies and returns false.
	 * 
	 * @return {@code true} if the setting of behaviours is complete, an
	 *         exception otherwise
	 * @throws BehaviourException
	 *             thrown if the behaviour setting cannot be complete
	 */
	public boolean checkAndBuid() throws BehaviourException {
		/*
		 * A: set of dependencies B: set of defined processes. if A-B in not
		 * empty, there are some dependencies unresolved.
		 */
		Set<String> A = new HashSet<String>();
		Set<String> B = new HashSet<String>();
		for (BehaviourTuple t : processes) {
			A.addAll(t.getDependencies());
			B.add(t.getName());
		}
		A.removeAll(B);
		if (A.size() > 0)
			throw new BehaviourException("SCParser - There following variables"
					+ " are undefined: " + A.toString());
		A = null;
		B = null;
		/* Resolves dependencies */
		resolveDependencies();
		/* TODO Reduces adjacent delays to a single one */
		// compactTimestep();
		return true;
	}

	// private void compactTimestep() {
	// for (ParserTuple t2 : processes) {
	// if (!t2.isVisible())
	// continue;
	// t2.setVisible(false);
	// compactTimes(t2);
	// }
	// }

	// private void compactTimes(ParserTuple t2) {
	// Behaviour b = t2.getState().getNext();
	// Behaviour parent;
	// while (!(b instanceof Delay) || parent.equals(b)) {
	// parent = b;
	// b = b.getNext();
	// }
	// Stack<Behaviour> delays = new Stack<Behaviour>();
	// delays.push(b);
	//
	// //
	// }

	/**
	 * Verifies that the passed variable is already in the PAFAS string
	 * structure.
	 * 
	 * @param variable
	 *            the name of the variable to search for
	 * @return <code>true</code> if the variable is already in the PAFAS
	 *         structure, <code>false</code>otherwise
	 */
	public boolean contains(String variable) {
		for (BehaviourTuple row : processes)
			if (row.getName().equals(variable))
				return true;
		return false;
	}

	/**
	 * Returns the table row with the specified index.
	 * 
	 * @param i
	 *            the index of the <code>TableTuple</code> to search and return
	 * @return the <code>PafasTuple</code> associated with index passed in
	 *         signature or <code>null</code> if the index is negative of out of
	 *         the current table size
	 */
	public BehaviourTuple getRow(int i) {
		if (i < processes.size() && i > 0)
			return processes.get(i);
		return null;
	}

	/**
	 * Search the table rows with the specified String name as a key to find the
	 * corresponding <code>PafasTuple</code> object row.
	 * 
	 * @param i
	 *            the string to search in each column of the table.
	 * @return the <code>PafasTuple</code> associated with the string passed in
	 *         signature or <code>null</code> if the searched row is not found
	 */
	public BehaviourTuple getRow(String i) {
		for (BehaviourTuple line : processes)
			if (line.getName().equalsIgnoreCase(i))
				return line;
		// variable searched not found so, return null...
		return null;
	}

	/**
	 * Returns the size of the table. This method considers ONLY the visible
	 * rows.
	 * 
	 * @return the number of visible rows
	 */
	public int getRowCount() {
		int x = 0;
		for (BehaviourTuple line : processes)
			if (line.isVisible())
				x++;
		return x;
	}

	/**
	 * Return a copy of tuples set.
	 * 
	 * @return an ArrayList of <code>PafasTuple</code>
	 */
	public ArrayList<BehaviourTuple> getRows() {
		return new ArrayList<BehaviourTuple>(processes);
	}

	@Override
	public String toString() {
		String string = "* Num of Rows: " + processes.size() + "\n";
		for (BehaviourTuple line : processes)
			string += line.toString() + "\n";
		return string;
	}

}